using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;

namespace MongoScienceDAL.Core
{
    public static class MongoQueryBuilder
    {
        public static IMongoQuery Process(QueryCriteria criteria)
        {
            if (criteria.Filters.Count == 0)
            {
                return Query.Null;
            }
            
            var mongoQueries = new List<IMongoQuery>();

            foreach (var queryFilter in criteria.Filters)
            {
                var key = queryFilter.Key;

                switch (queryFilter.Type)
                {
                        //General Related
                    case QueryFilterType.HasField:
                        mongoQueries.Add(Query.Exists(key));
                        break;
                    case QueryFilterType.DosentHaveField:
                        mongoQueries.Add(Query.NotExists(key));
                        break;

                        //Text Related
                    case QueryFilterType.TextContains:
                        var mongoRegexTextContains =
                            new BsonRegularExpression(string.Format(".*{0}.*", queryFilter.Data["text"]), "i");
                        mongoQueries.Add(Query.Matches(key, mongoRegexTextContains));
                        break;
                    case QueryFilterType.TextStartsWith:
                        var mongoRegexTextStartsWith =
                            new BsonRegularExpression(string.Format("^{0}", queryFilter.Data["text"]), "i");
                        mongoQueries.Add(Query.Matches(key, mongoRegexTextStartsWith));
                        break;
                    case QueryFilterType.TextEndsWith:
                        var mongoRegexTextEndsWith =
                            new BsonRegularExpression(string.Format("{0}$", queryFilter.Data["text"]), "i");
                        mongoQueries.Add(Query.Matches(key, mongoRegexTextEndsWith));
                        break;
                    case QueryFilterType.TextEquals:
                        mongoQueries.Add(Query.EQ(key, BsonValue.Create(queryFilter.Data["text"])));
                        break;
                    case QueryFilterType.TextRegex:
                        var mongoRegexTextRegex = new BsonRegularExpression((string) queryFilter.Data["text"], "i");
                        mongoQueries.Add(Query.Matches(key, mongoRegexTextRegex));
                        break;

                        //Date Related
                    case QueryFilterType.DateEquals:
                        mongoQueries.Add(Query.EQ(key, BsonDateTime.Create((DateTime) queryFilter.Data["date"])));
                        break;
                    case QueryFilterType.DateGreaterThan:
                        mongoQueries.Add(Query.GT(key, BsonDateTime.Create((DateTime) queryFilter.Data["date"])));
                        break;
                    case QueryFilterType.DateGreaterThanOrEquals:
                        mongoQueries.Add(Query.GTE(key, BsonDateTime.Create((DateTime)queryFilter.Data["date"])));
                        break;
                    case QueryFilterType.DateLessThan:
                        mongoQueries.Add(Query.LT(key, BsonDateTime.Create((DateTime)queryFilter.Data["date"])));
                        break;
                    case QueryFilterType.DateLessThanOrEquals:
                        mongoQueries.Add(Query.LTE(key, BsonDateTime.Create((DateTime)queryFilter.Data["date"])));
                        break;
                    case QueryFilterType.DateInsideRange:
                        var highQueryInsideRange = Query.LTE(key, BsonDateTime.Create((DateTime) queryFilter.Data["enddate"]));
                        var lowQueryInsideRange = Query.GTE(key, BsonDateTime.Create((DateTime)queryFilter.Data["startdate"]));
                        mongoQueries.Add(Query.And(highQueryInsideRange, lowQueryInsideRange));
                        break;
                    case QueryFilterType.DateOutsideRange:
                        var highQueryOutsideRange = Query.GT(key, BsonDateTime.Create((DateTime) queryFilter.Data["enddate"]));
                        var lowQueryOutsideRange = Query.LT(key, BsonDateTime.Create((DateTime)queryFilter.Data["startdate"]));
                        mongoQueries.Add(Query.Or(highQueryOutsideRange, lowQueryOutsideRange));
                        break;

                        //Value Related
                    case QueryFilterType.ValueEquals:
                        mongoQueries.Add(Query.EQ(key, BsonDouble.Create((double) queryFilter.Data["value"])));
                        break;
                    case QueryFilterType.ValueGreaterThan:
                        mongoQueries.Add(Query.GT(key, BsonDouble.Create((double)queryFilter.Data["value"])));
                        break;
                    case QueryFilterType.ValueGreaterThanOrEquals:
                        mongoQueries.Add(Query.GTE(key, BsonDouble.Create((double)queryFilter.Data["value"])));
                        break;
                    case QueryFilterType.ValueLessThan:
                        mongoQueries.Add(Query.LT(key, BsonDouble.Create((double)queryFilter.Data["value"])));
                        break;
                    case QueryFilterType.ValueLessThanOrEquals:
                        mongoQueries.Add(Query.LTE(key, BsonDouble.Create((double)queryFilter.Data["value"])));
                        break;

                        //ObjectId Related
                    case QueryFilterType.ObjectIdEquals:
                        mongoQueries.Add(Query.EQ("_id", (ObjectId) queryFilter.Data["value"]));
                        break;
                    case QueryFilterType.ObjectIdEqualsAny:
                        mongoQueries.Add(Query.In("_id",
                                                  ((List<ObjectId>) queryFilter.Data["value"]).Select(
                                                      BsonObjectId.Create).AsEnumerable()));
                        break;
                    case QueryFilterType.ParentIdEquals:
                        mongoQueries.Add(Query.EQ(key, (ObjectId)queryFilter.Data["value"]));
                        break;
                    case QueryFilterType.ParentIdEqualsAny:
                        mongoQueries.Add(Query.In(key,
                                                  ((List<ObjectId>)queryFilter.Data["value"]).Select(
                                                      BsonObjectId.Create).AsEnumerable()));
                        break;
                    case QueryFilterType.ChildIdsContain:
                        mongoQueries.Add(Query.EQ(key, (ObjectId)queryFilter.Data["value"]));
                        break;
                    case QueryFilterType.ChildIdsContainAny:
                        mongoQueries.Add(Query.In(key,
                                                  ((List<ObjectId>)queryFilter.Data["value"]).Select(
                                                      BsonObjectId.Create).AsEnumerable()));
                        break;

                        //Boolean Related
                    case QueryFilterType.BooleanEquals:
                        mongoQueries.Add(Query.EQ(key, BsonBoolean.Create(queryFilter.Data["value"])));
                        break;
                        
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }

            if (mongoQueries.Count == 0)
                return Query.Null;

            var finalQuery = Query.And(mongoQueries.ToArray());

            return finalQuery;
        }
    }
}